﻿#region Using

using System;
using System.Collections.Generic;
using System.Linq;

using Common.Base;
using Common.Enums;

using GameCommon.Args;
using GameCommon.ComponentInterfaces;
using GameCommon.Components;
using GameCommon.Effects;
using GameCommon.Manager;
using GameCommon.ManagerInterfaces;

using Microsoft.Xna.Framework.Graphics;

#endregion

namespace GameCommon.Base
{
    public abstract class ScreenBase : IScreen, IDisposable
    {
        private readonly IScreenChanger changer;

        protected readonly object Argument;

        private readonly Camera camera;

        private readonly Dictionary<string, Action<GameComponent>> commands;

        private bool enabled;

        public ScreenBase(IScreenChanger changer, object argument)
        {
            this.Components = new ComponentCollection();
            this.Animations = new AnimationCollection();
            this.Effects = new List<IEffect>();

            this.enabled = true;
            this.Layer = 1;
            this.changer = changer;
            this.Argument = argument;
            this.commands = new Dictionary<string, Action<GameComponent>>();
            this.camera = new Camera();

            this.ScreenAsTexture = Managers.ContentManager.GetRenderTarget2D();

            Managers.MouseManager.MouseButtonPressed += this.OnMouseButtonPressed;
            Managers.MouseManager.MouseMoved += this.OnMouseMoved;
            Managers.MouseManager.MouseDraggedLeft += this.OnMouseDraggedLeft;
            Managers.MouseManager.MouseDraggedRight += this.OnMouseDraggedRight;
        }

        public abstract ScreenEnum Screen { get; }

        public int Layer { get; set; }

        public Camera Camera
        {
            get
            {
                return this.camera;
            }
        }

        public bool IsEnabled
        {
            get
            {
                return this.enabled;
            }
        }

        public IList<IEffect> Effects { get; private set; }

        public RenderTarget2D ScreenAsTexture { get; private set; }

        protected ComponentCollection Components { get; set; }

        protected AnimationCollection Animations { get; private set; }

        /// <summary>
        /// Draws Gamecomponents and HitBoxes
        /// </summary>
        /// <param name="manager">DrawingManager to add Components</param>
        /// <param name="gameTime">Monogame Gametime</param>
        public void Draw(IDrawingManager manager, Microsoft.Xna.Framework.GameTime gameTime)
        {
            foreach (GameComponent gameComponent in this.Components)
            {
                gameComponent.Draw(manager, gameTime);

                //// TODO Remove HitBox drawing
                var hitable = gameComponent as IHitable;
                if (hitable != null && hitable.HitBox != null)
                {
                    hitable.HitBox.Draw(manager, gameTime);
                }
            }
        }

        public virtual void Initialize()
        {
        }

        public virtual void LoadContent()
        {
        }

        /// <summary>
        /// Updates Components
        /// Updates Animations
        /// Updates Camera Matrix
        /// </summary>
        /// <param name="gameTime">Monogame Gametime</param>
        public virtual void Update(Microsoft.Xna.Framework.GameTime gameTime)
        {
            this.Components.Update(gameTime);
            this.Animations.Update(gameTime);

            this.camera.UpdateMatrix();
        }
        
        public virtual void Dispose()
        {
        }

        protected IScreen AddScreen(ScreenEnum screen, object arg = null)
        {
            if (this.changer != null)
            {
                return this.changer.AddScreen(screen, arg);
            }

            return null;
        }

        protected void RemoveScreen(ScreenEnum screen)
        {
            if (this.changer != null)
            {
                this.changer.RemoveScreen(screen);
            }
        }

        protected void RemoveAnyScreen()
        {
            if (this.changer != null)
            {
                this.changer.RemoveAnyScreen();
            }
        }

        /// <summary>
        /// Adds a method which is called after clicking a Hitbox
        /// </summary>
        /// <param name="key">Command name</param>
        /// <param name="method">Method to call</param>
        protected void AddCommand(string key, Action<GameComponent> method)
        {
            this.commands.Add(key, method);
        }

        protected void Disable()
        {
            Managers.MouseManager.MouseButtonPressed -= this.OnMouseButtonPressed;
            Managers.MouseManager.MouseMoved -= this.OnMouseMoved;
            Managers.MouseManager.MouseDraggedLeft -= this.OnMouseDraggedLeft;

            foreach (GameComponent component in this.Components)
            {
                component.Disable();
            }

            this.enabled = false;
        }

        protected void Enable()
        {
            Managers.MouseManager.MouseButtonPressed += this.OnMouseButtonPressed;
            Managers.MouseManager.MouseMoved += this.OnMouseMoved;
            Managers.MouseManager.MouseDraggedLeft += this.OnMouseDraggedLeft;

            foreach (GameComponent component in this.Components)
            {
                component.Enable();
            }

            this.enabled = true;
        }

        protected virtual void OnMouseDraggedLeft(MouseDraggedArgs args)
        {
        }

        protected virtual void OnMouseDraggedRight(MouseDraggedArgs args)
        {
        }

        private void OnMouseButtonPressed(MouseButtonArgs args)
        {
            if (args.MouseButton == MouseButtonEnum.Left)
            {
                var pos = this.Camera.GetPosition(args.Position);
                this.EvaluateHitBoxes(pos);
            }
        }

        private void EvaluateHitBoxes(Position position)
        {
            foreach (var component in this.Components.OfType<IHitable>())
            {
                var hitbox = component.HitBox;

                if (hitbox != null && hitbox.IsPointInHitbox(position))
                {
                    Console.WriteLine("ScreenBase: Hitbox triggered " + component.ToString());
                    Action<GameComponent> command;
                    if (hitbox.Command != null && this.commands.TryGetValue(hitbox.Command, out command))
                    {
                        command.Invoke(component as GameComponent);
                    }
                }
            }
        }

        private void OnMouseMoved(MouseMovedArgs args)
        {
            var pos = this.Camera.GetPosition(args.Position);

            foreach (var component in this.Components.OfType<IHitable>())
            {
                var hitbox = component.HitBox;

                if (hitbox == null)
                {
                    continue;
                }

                if (hitbox.IsPointInHitbox(pos))
                {
                    hitbox.IsMouseOver = true;
                    continue;
                }

                hitbox.IsMouseOver = false;
            }
        }
    }
}